/*
 * 描述： <描述>
 * 修改人： rain
 * 修改时间： 2016年9月12日
 * 项目： p2p-taikongma
 */
package com.rainhy.webcore.token.context;

import java.util.Date;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.web.servlet.ModelAndView;

import com.rainhy.webcore.token.mock.MockToken;


/**
 * AccessToken容器
 * 
 * @author rain
 * @version [版本号, 2016年9月12日]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
public class AccessTokenContext extends AccessTokenContextConfigurator implements InitializingBean {
    
    public static final String TOKEN_NAME = "Token";
    
    /** 自身唯一引用 */
    private static AccessTokenContext context;
    
    /** 线程变量:当前日志会话 */
    private static ThreadLocal<Token> currentToken = new ThreadLocal<Token>();
    
    /**
     * 结束一个请求
     *
     * @param request
     * @param response
     * 
     * @return void [返回类型说明]
     * @exception [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年9月13日]
     * @author rain
     */
    public static void endRequest(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        currentToken.remove();
    }
    
    /** 返回自身唯一引用 */
    public static AccessTokenContext getContext() {
        return AccessTokenContext.context;
    }
    
    /** 返回当前 token */
    public static Token getToken() {
        return AccessTokenContext.getContext().get();
    }
    
    /**
     * 完成一个请求
     *
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * 
     * @return void [返回类型说明]
     * @exception [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年9月14日]
     * @author rain
     */
    public static void postRequest(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        Token token = currentToken.get();
        if (token != null) {
            TokenManager tokenManager = AccessTokenContext.getContext().getTokenManager();
            tokenManager.persist(token);
        }
    }
    
    /**
     * 开始一个请求
     *
     * @param request
     * @param response
     * @param handler
     * 
     * @return void [返回类型说明]
     * @exception [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年9月13日]
     * @author rain
     */
    public static void startRequest(HttpServletRequest request, HttpServletResponse response, Object handler) {
        currentToken.remove();
        
        AccessTokenContext context = AccessTokenContext.getContext();
        String tokenId = context.getTokenIdFromRequest(request);
        Token token = null;
        
        ///// 查找 token ,确认 token 是否已经失效
        if (StringUtils.isNotBlank(tokenId)) {
            TokenManager tokenManager = AccessTokenContext.getContext().getTokenManager();
            token = tokenManager.findToken(tokenId);
            if (token != null && token.getLastAccessedTime() + token.getMaxInactiveInterval() < new Date().getTime()) {  // 已经失效
                context.invalidate(token);
                token = null;
            }
        }
        
        if (token == null) {    // 没有找到 token
            token = new MockToken(request, response);
            token.setMaxInactiveInterval(context.getMaxInactiveInterval());
        } else {    // 找到 Token
            token = new MockToken(token, request, response);
            MockToken mockToken = (MockToken) token;
            mockToken.access();
        }
        
        context.setTokenToResponse(token, response);
        currentToken.set(token);
    }
    
    @Override
    public void afterPropertiesSet() throws Exception {
        AccessTokenContext.context = this;
    }
    
    /** 获取当前 token,如果不存在,则创建一个 */
    public Token get() {
        return currentToken.get();
    }
    
    /** 让 token */
    public void invalidate() {
        MockToken mockToken = (MockToken) get();
        invalidate(mockToken);
        mockToken.changeTokenId();
        mockToken.access();
        mockToken.setMaxInactiveInterval(maxInactiveInterval);
        setTokenToResponse(mockToken, mockToken.getTokenContext().getResponse());
    }
    
    /**
     * 
     * 从 request 中获取 tokenid<br>
     * 优先从 http 协议的 header 中获取 token,如果没有,则从 parameter 中获取
     *
     * @param request
     * 
     * @return String [返回类型说明]
     * @exception [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年9月13日]
     * @author rain
     */
    private String getTokenIdFromRequest(HttpServletRequest request) {
        
        // 优先从 header 中获取 Token
        if (validateHeader) {
            String headerToken = StringUtils.trimToNull(request.getHeader(AccessTokenContext.TOKEN_NAME));
            if (StringUtils.isNotBlank(headerToken)) {
                return headerToken;
            }
        }
        
        // 接着中 cookie 中获取 Token
        if (validateCookie) {
            Cookie[] cookies = request.getCookies();
            if (ArrayUtils.isNotEmpty(cookies)) {
                for (Cookie cookie : cookies) {
                    if (StringUtils.equalsIgnoreCase(cookie.getName(), AccessTokenContext.TOKEN_NAME)) {
                        return cookie.getValue();
                    }
                }
            }
        }
        
        // 最后从 parameter 中获取 Token
        return StringUtils.trimToNull(request.getParameter(AccessTokenContext.TOKEN_NAME));
    }
    
    /** 让 token 无效 */
    private void invalidate(Token token) {
        token.invalidate();
        getTokenManager().persist(token);
    }
    
    /**
     * 
     * 把 tokenid 设置到 Response中<br>
     * 直接设置到 http 协议中的 header 中,和放到 cookie 中
     *
     * @param tokenId
     * @param response
     * 
     * @return void [返回类型说明]
     * @exception [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年9月14日]
     * @author rain
     */
    private void setTokenToResponse(Token token, HttpServletResponse response) {
        if (validateHeader) {
            response.setHeader(AccessTokenContext.TOKEN_NAME, token.getId());
        }
        if (validateCookie) {
            response.addCookie(new Cookie(AccessTokenContext.TOKEN_NAME, token.getId()));
        }
    }
}
